from math import sin, cos, pi

import pygame
import basicSprite

displayWidth = 640
displayHeight = 480
fpsLimit = 90

def sinInterpolation(start, end, steps=30):
    """
    returns a list of angles
    """
    values = [start]
    delta = end - start
    for i in range(1, steps):
        n = (pi / 2.0) * (i / float(steps - 1))
        values.append(start + delta * sin(n))
    return values

class RotatingMenu:
    def __init__(self, x, y, radius, arc=pi*2, defaultAngle=0, wrap=False, rotate=False, visible=True):
        """
        @param x:
            The horizontal center of this menu in pixels.
        
        @param y:
            The vertical center of this menu in pixels.
        
        @param radius:
            The radius of this menu in pixels. Note: This is the size
                of the circular path in which the elements are placed.
                The actual size of the menu may vary depending on item
                sizes.

        @param arc:
            The arc in radians which the menu covers. pi*2 is a full
                circle.
        
        @param defaultAngle:
            The angle at which the selected item is found.
        
        @param wrap:
            Whether the menu should select the first item after the last
                one or stop.

        @param rotate:
            Whether the menu should rotate when the selection changes.
        """
        self.x = x
        self.y = y
        self.radius = radius
        self.arc = arc
        self.defaultAngle = defaultAngle
        self.wrap = wrap
        self.rotate = rotate
        
        self.rotation = 0
        self.rotationTarget = 0
        self.rotationSteps = [] #Used for interpolation
        
        self.items = []
        self.itemSprites = pygame.sprite.RenderUpdates()
        self.selectedItem = None
        self.selectedItemNumber = 0
        self.visible = visible
    
    def addItem(self, item):
        """
        @param item:
            The MenuItem to be added to the RotatingMenu.
        """
        self.items.append(item)
        self.itemSprites.add(item)
        if len(self.items) == 1:    # if the added item is the only item in the menu, select it
            self.selectedItem = item
            self.selectItem(0)

    def selectNextItem(self):
        actualAngle = self.defaultAngle % (2.0 * pi)
        if actualAngle < pi or actualAngle > (3.0 * pi / 2.0) or self.rotate:
            self.selectItem(self.selectedItemNumber + 1)
        else:
            self.selectItem(self.selectedItemNumber - 1)

    def selectPrevItem(self):
        actualAngle = self.defaultAngle % (2.0 * pi)
        if actualAngle < pi or actualAngle > (3.0 * pi / 2.0) or self.rotate:
            self.selectItem(self.selectedItemNumber - 1)
        else:
            self.selectItem(self.selectedItemNumber + 1)
    
    def selectItem(self, itemNumber):
        if self.wrap == True:
            if itemNumber > len(self.items) - 1: itemNumber = 0
            if itemNumber < 0: itemNumber = len(self.items) - 1
        else:
            itemNumber = min(itemNumber, len(self.items) - 1)
            itemNumber = max(itemNumber, 0)
        
        self.selectedItem.deselect()
        self.selectedItem = self.items[itemNumber]
        self.selectedItem.select()
        
        self.selectedItemNumber = itemNumber
        
        if self.rotate:
            self.rotationTarget = - self.arc * (itemNumber / float(len(self.items)))
            self.rotationSteps = sinInterpolation(self.rotation,
                                              self.rotationTarget, 45)
    
    def rotateMenu(self, angle=0):
        """
        @param angle: The angle in radians by which the menu is rotated.
        By default, does not rotate.
        """
        for i in range(len(self.items)):
            item = self.items[i]
            n = i / float(len(self.items)-1)
            rot = self.defaultAngle + angle + self.arc * n
            
            item.rect.centerx = self.x + cos(rot) * self.radius
            item.rect.centery = self.y + sin(rot) * self.radius
    
    def update(self):
        if len(self.rotationSteps) > 0 and self.rotate:
            self.rotation = self.rotationSteps.pop(0)
        self.rotateMenu(self.rotation)
        self.itemSprites.update()
        if not self.visible:
            for sprite in self.itemSprites:
                sprite.image = pygame.Surface(sprite.rect.size)
                sprite.image.set_alpha(0)
            
    
    def close(self):
        for sprite in self.itemSprites:
            sprite.kill()
            

class MenuItem(basicSprite.TextSprite):
    def __init__(self, text="Spam", noselectColor=(255,255,255),
                 selectColor=(255,0,0), fontArg=None, noselectSize=20,
                 selectSize=40):
        self.noselectColor = noselectColor
        self.selectColor = selectColor
        self.noselectSize = noselectSize
        self.selectSize = selectSize
        basicSprite.TextSprite.__init__(self, (0,0), text, 
                self.noselectColor, fontArg, self.noselectSize)
            # Sets self.text, self.color, self.font, self.fontsize,
            # self.image, self.rect
    
    def select(self):
        """Just visual stuff"""
        self.color = self.selectColor
        self.fontsize = self.selectSize
    
    def deselect(self):
        """Just visual stuff"""
        self.color = self.noselectColor
        self.fontsize = self.noselectSize
        
def main():
    pygame.init()
    
    display = pygame.display.set_mode((displayWidth, displayHeight))
    clock = pygame.time.Clock()
    
    menu = RotatingMenu(x=320, y=240, radius=220, arc=pi, defaultAngle=pi/2.0, rotate=True)
    
    for i in range(10):
        menu.addItem(MenuItem("Spam" + str(i)))
    menu.selectItem(0)
    
    #Loop
    while True:
        #Handle events
        events = pygame.event.get()
        for event in events:
            if event.type == pygame.QUIT:
                return False
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_LEFT:
                    menu.selectItem(menu.selectedItemNumber + 1)
                if event.key == pygame.K_RIGHT:
                    menu.selectItem(menu.selectedItemNumber - 1)
                if event.key == pygame.K_SPACE:
                    menu.visible = not menu.visible
        
        #Update stuff
        menu.update()
        
        #Draw stuff
        menu.itemSprites.clear(display, pygame.Surface(display.get_size()))
        reclist = menu.itemSprites.draw(display)
        pygame.display.update(reclist)

if __name__ == "__main__":
    main()
